Fallstudie: Quantitative Methoden in der Saftproduktion¶

WING - HS25 - Prof. Adrian Stämpfli

Kontext¶

Die Apfelsaft GmbH wurde im Jahr 1995 in einem malerischen Dorf in der Schweiz gegründet. Das Unternehmen begann als kleiner Familienbetrieb, der sich auf die Produktion von hochwertigem Apfelsaft aus regionalen Äpfeln spezialisierte. Mit einer Leidenschaft für Qualität und Nachhaltigkeit hat sich die Apfelsaft GmbH schnell einen Namen gemacht und ist heute einer der führenden Hersteller von Apfelsaft in der Region.

Die Apfelsaft GmbH produziert eine breite Palette von Apfelsaftprodukten, darunter naturtrüben Apfelsaft, klaren Apfelsaft und verschiedene Mischsäfte mit anderen Früchten. Das Unternehmen legt grossen Wert auf die Verwendung von lokal angebauten Äpfeln und setzt auf umweltfreundliche Produktionsmethoden. Die Produktionsstätte ist mit modernster Technologie ausgestattet, um höchste Qualitätsstandards zu gewährleisten.

Teilaufgabe: Analyse von Verkaufsdaten¶

Du bist Data Scientist (Wirtschaftsingenieur, Qualitätssicherer, Business Developer, Product Manager) bei der Apfelsaft GmbH und hast die Aufgabe den Verkauf des Obstsaftes besser zu verstehen. Zu diesem Zweck hast du Daten aus dem Verkauf von euren Saftprodukten aus dem Jahr 2023.

Die Daten enthalten Zeilen zu einzelnen Verkaufstransaktionen eines Produkts. Die Verkaufstransaktion findet an einem Ort (lat, lng) statt, generiert einen Umsatz (umsatz), betrifft ein Produkt (produkt) und findet zu einem bestimmten Zeitpunkt (monat) statt.

Folgenden initialien Fragen kannst Du nachgehen: Wann wird Saft verkauft? Welche Produkte? Wie viel wird verkauft? Wo wird verkauft? Welche Produkte laufen wo besonders gut?

In diesem Jupyter Notebook kannst du mithilfe einer Explorativen Datenanalyse diesen Fragen nachgehen.

Aufbau des Notebooks¶

Im Notebook werden zuerst fiktive Verkaufsdaten generiert. Den Code, welcher die Daten generiert darfst du ignorieren. Deine Aufgabe startet mit dem fertig erstellten Data Frame.

Danach kommt die Explorative Datenanalyse.

Zuerst verschaffst du dir einen Überblick über die Daten. Danach gehst du den einzelnen Fragen nach und vertiefst sie.

Laden der Libraries und Erstellen der fiktiven Verkaufsdaten¶

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# ---------------------------
# 1) Daten generieren
# ---------------------------
np.random.seed(42)

# Parameter
produkte = ["Apfelsaft Klar", "Apfelsaft Trüb", "Apfel-Schorle"]
monate = pd.date_range("2023-01-01", "2023-12-31", freq="MS")

# Cluster-Infos (lat, lng, Varianz, Gewicht, Produkt-Bias)
cluster_info = [
    {"center": (50.0, 8.0), "scale": 0.2, "anzahl": 400, "bias": None},
    {"center": (52.5, 13.4), "scale": 0.25, "anzahl": 500, "bias": "Apfel-Schorle"},
    {"center": (48.1, 11.6), "scale": 0.15, "anzahl": 100, "bias": None},  # Weniger Verkäufe
    {"center": (53.6, 9.9), "scale": 0.2, "anzahl": 350, "bias": None}
]

records = []

for cluster in cluster_info:
    lat_center, lng_center = cluster["center"]
    for i in range(cluster["anzahl"]):
        lat = np.random.normal(lat_center, cluster["scale"])
        lng = np.random.normal(lng_center, cluster["scale"])
        monat = np.random.choice(monate)
        if cluster["bias"]:
            produkt = np.random.choice(produkte, p=[0.1, 0.1, 0.8])  # Bias für ein Produkt
        else:
            produkt = np.random.choice(produkte)
        umsatz = np.random.randint(100, 1000)
        records.append([lat, lng, umsatz, produkt, monat])

df = pd.DataFrame(records, columns=["lat", "lng", "umsatz", "produkt", "monat"])

1. Erste Analysen¶

1.1 Überblick über den Datensatz¶
In [2]:
df.info()
df.describe()
df.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1350 entries, 0 to 1349
Data columns (total 5 columns):
 #   Column   Non-Null Count  Dtype         
---  ------   --------------  -----         
 0   lat      1350 non-null   float64       
 1   lng      1350 non-null   float64       
 2   umsatz   1350 non-null   int64         
 3   produkt  1350 non-null   object        
 4   monat    1350 non-null   datetime64[ns]
dtypes: datetime64[ns](1), float64(2), int64(1), object(1)
memory usage: 52.9+ KB
Out[2]:
lat lng umsatz produkt monat
0 50.099343 7.972347 120 Apfelsaft Klar 2023-11-01
1 49.953169 7.953173 187 Apfel-Schorle 2023-11-01
2 49.822954 7.917562 408 Apfelsaft Trüb 2023-03-01
3 49.883824 7.894966 559 Apfelsaft Klar 2023-05-01
4 50.004444 7.914441 289 Apfel-Schorle 2023-10-01
1.2 Verteilung der Verkäufe nach Produkt¶
In [3]:
sns.countplot(data=df, x='produkt', palette='pastel')
plt.title('Anzahl Verkaufstransaktionen pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Anzahl Verkäufe')
plt.xticks(rotation=15)
plt.show()
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\547875242.py:1: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.countplot(data=df, x='produkt', palette='pastel')
No description has been provided for this image
1.3 Gesamtumsatz pro Produkt¶
In [4]:
sns.barplot(
    data=df, x='produkt', y='umsatz',
    estimator=sum, errorbar=None,
    hue='produkt', palette='Blues_d',
    dodge=False, legend=False
)
plt.title('Gesamtumsatz pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Gesamtumsatz (CHF)')
plt.xticks(rotation=15)
plt.show()
No description has been provided for this image
1.4 Durchschnittlicher Umsatz pro Produkt¶
In [5]:
sns.barplot(
    data=df, x='produkt', y='umsatz',
    estimator=pd.Series.mean, errorbar=None,
    hue='produkt', palette='crest',
    dodge=False, legend=False
)
plt.title('Durchschnittlicher Umsatz pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Ø Umsatz (CHF)')
plt.xticks(rotation=15)
plt.show()
No description has been provided for this image
1.5 Zeitliche Entwicklung der Umsätze¶
In [6]:
df['monat_num'] = df['monat'].dt.month
sns.lineplot(data=df, x='monat_num', y='umsatz', hue='produkt', marker='o')
plt.title('Zeitliche Entwicklung der Umsätze nach Produkt')
plt.xlabel('Monat')
plt.ylabel('Umsatz (CHF)')
plt.show()

umsatz_monat_prod = df.groupby(['monat_num', 'produkt'], as_index=False)['umsatz'].sum()
sns.lineplot(data=umsatz_monat_prod, x='monat_num', y='umsatz', hue='produkt', marker='o', errorbar=None)
plt.title('Gesamtumsatz pro Monat und Produkt')
plt.xlabel('Monat')
plt.ylabel('Umsatz (CHF)')
plt.show()
No description has been provided for this image
No description has been provided for this image

2. Vertiefende Analysen¶

2.1 Geografische Verteilung der Verkäufe¶
In [7]:
sns.scatterplot(
    data=df, x='lng', y='lat',
    size='umsatz', hue='produkt',
    alpha=0.7, palette='Set2'
)
plt.title('Geografische Verteilung der Verkäufe')
plt.xlabel('Längengrad')
plt.ylabel('Breitengrad')
plt.legend(title='Produkt', bbox_to_anchor=(1.05, 1), loc='upper left')
plt.show()
No description has been provided for this image
In [8]:
# 1. Cluster-Optimierung: beste Clusterzahl bestimmen
X = df[['lat', 'lng']]
silhouette_scores = []
cluster_range = range(2, 11)

for k in cluster_range:
    model = KMeans(n_clusters=k, random_state=42)
    labels = model.fit_predict(X)
    silhouette_scores.append(silhouette_score(X, labels))

best_k = cluster_range[np.argmax(silhouette_scores)]
print(f"Optimale Clusteranzahl: {best_k}")

# 2. KMeans mit optimaler Clusterzahl
kmeans = KMeans(n_clusters=best_k, random_state=42)
df['cluster'] = kmeans.fit_predict(X)

# 3. Hauptkarte: alle Cluster
plt.figure(figsize=(8, 6))
sns.scatterplot(
    data=df, x='lng', y='lat', hue='cluster', size='umsatz',
    sizes=(50, 300), palette='viridis', alpha=0.7
)
plt.title('Geografische Cluster der Verkaufsstandorte')
plt.xlabel('Längengrad')
plt.ylabel('Breitengrad')
plt.legend(title='Cluster')
plt.show()

# 4. Cluster-Zentren markieren
centers = kmeans.cluster_centers_
plt.figure(figsize=(8,6))
for i, (lat, lng) in enumerate(centers):
    plt.scatter(lng, lat, c='red', s=200, marker='X')
    plt.text(lng+0.05, lat+0.05, f'Cluster {i}', color='red', fontsize=10, weight='bold')
plt.title('Cluster-Zentren der Verkaufsstandorte')
plt.xlabel('Längengrad')
plt.ylabel('Breitengrad')
plt.legend(['Cluster-Zentren'])
plt.show()

# 5. Separate Karten je Cluster mit Produkthervorhebung + Barplots
for c in sorted(df['cluster'].unique()):
    cluster_data = df[df['cluster'] == c]

    # Streudiagramm mit Produkten
    plt.figure(figsize=(7, 5))
    sns.scatterplot(
        data=cluster_data, x='lng', y='lat', hue='produkt',
        size='umsatz', sizes=(40, 250), alpha=0.8
    )
    plt.title(f'Cluster {c}: Produktverteilung')
    plt.xlabel('Längengrad')
    plt.ylabel('Breitengrad')
    plt.legend(title='Produkt', loc='best')
    plt.show()

    # Barplot: Durchschnittlicher Umsatz pro Produkt in diesem Cluster
    plt.figure(figsize=(6,4))
    sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
    plt.title(f'Cluster {c}: Umsatz pro Produkt')
    plt.xlabel('Produkt')
    plt.ylabel('Umsatz (CHF)')
    plt.show()
Optimale Clusteranzahl: 4
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
No description has been provided for this image
No description has been provided for this image
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
No description has been provided for this image
No description has been provided for this image
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
No description has been provided for this image
No description has been provided for this image
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
No description has been provided for this image
2.2 Regionale Umsatzschwerpunkte¶
In [9]:
df['region'] = df['lat'].round(0).astype(str) + '/' + df['lng'].round(0).astype(str)
region_umsatz = df.groupby(['region', 'produkt'])['umsatz'].mean().reset_index()

sns.barplot(
    data=region_umsatz, x='region', y='umsatz',
    hue='produkt', palette='muted',
    estimator=pd.Series.mean, errorbar=None
)
plt.title('Durchschnittlicher Umsatz pro Region und Produkt')
plt.xlabel('Region (gerundet)')
plt.ylabel('Ø Umsatz (CHF)')
plt.xticks(rotation=45)
plt.legend(title='Produkt')
plt.show()
No description has been provided for this image
2.3 Boxplot – Umsatzverteilung pro Produkt¶
In [10]:
sns.boxplot(
    data=df, x='produkt', y='umsatz', palette='cool'
)
plt.title('Umsatzverteilung pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Umsatz (CHF)')
plt.show()
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\2785082716.py:1: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.boxplot(
No description has been provided for this image
2.4 Monatlicher Durchschnittsumsatz (alle Produkte)¶
In [11]:
monthly_avg = df.groupby(df['monat'].dt.month)['umsatz'].mean().reset_index()
sns.lineplot(data=monthly_avg, x='monat', y='umsatz', marker='o', color='darkgreen')
plt.title('Monatlicher Durchschnittsumsatz (alle Produkte)')
plt.xlabel('Monat')
plt.ylabel('Ø Umsatz (CHF)')
plt.show()
No description has been provided for this image
2.5 Produktanteile am Gesamtumsatz¶
In [12]:
umsatz_anteile = df.groupby('produkt')['umsatz'].sum().reset_index()
umsatz_anteile['Anteil (%)'] = 100 * umsatz_anteile['umsatz'] / umsatz_anteile['umsatz'].sum()

sns.barplot(
    data=umsatz_anteile, x='produkt', y='Anteil (%)',
    hue='produkt', palette='viridis',
    dodge=False, legend=False,
    estimator=pd.Series.mean, errorbar=None
)
plt.title('Anteil am Gesamtumsatz nach Produkt')
plt.xlabel('Produkt')
plt.ylabel('Umsatzanteil (%)')
plt.show()
No description has been provided for this image

3. Fazit¶

  1. Zeitliche Muster
  • Alle drei Produkte werden ganzjährig verkauft, mit spürbaren Monat-zu-Monat-Schwankungen vom Umsatz.
  • Apfelsaft Klar und Apfel-Schorle zeigen das stabilste Umsatzprofil je Transaktion (geringe Streuung – enge Schattierungsbänder im Lineplot).
  • Der gesamte Umsatz pro Produkt und Monat ist gleich volatil bei allen Produkten.
  1. Räumliche Verteilung / regionale Schwerpunkte
  • Die Karte zeigt klare räumliche Cluster: Es existieren mehrere Hotspots, in denen sich Verkaufsereignisse ballen (dichte Punktwolken).
  • Überdurchschnittlicher Schorle-Anteil in einer Region, während zwei andere Regionen stärker von „Klar“ dominiert sind.
  • Ein Gebiet weist geringere Gesamtdichte/Umsätze auf (kleineres Absatzgebiet bzw. schwächere Präsenz).
  1. Produktmix & Performance
  • Schorle liefert die höchsten Umsätze.
  • Apfel-Schorle hat die höchsten Umsätze in der ersten Jahreshälfte. Klar und Trüb steigern die Umsätze in der zweiten Jahreshälfte
  1. Implikationen.
  • Vertrieb & Marketing: In Schorle-Hotspots gezielte Aktionen (Displays, Bundles), in „Klar“-Regionen Sortimentsbreite sichern.